package cn.ddiancan.auth.service.resolver;

import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.BearerTokenErrors;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.util.StringUtils;

@RequiredArgsConstructor
@Slf4j
public class XddBearerTokenResolver implements BearerTokenResolver {

    private static final String ACCESS_TOKEN_PARAMETER_NAME = "access_token";

    private static final Pattern authorizationPattern =
        Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$", Pattern.CASE_INSENSITIVE);

    private static final String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;

    private static final String accessTokenHeaderName = "x-access-token";

    private final OAuth2AuthorizationService oAuth2AuthorizationService;

    @Override
    public String resolve(final HttpServletRequest request) {
        // 标准的bearerToken解析方式
        final String authorizationHeaderToken = resolveFromAuthorizationHeader(request);
        final String parameterToken =
            isParameterTokenSupportedForRequest(request) ? resolveFromRequestParameters(request) : null;
        String virtualToken = null;
        if (authorizationHeaderToken != null) {
            if (parameterToken != null) {
                final BearerTokenError error =
                    BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");
                throw new OAuth2AuthenticationException(error);
            }
            virtualToken = authorizationHeaderToken;
        }
        if (parameterToken != null && isParameterTokenEnabledForRequest(request)) {
            virtualToken = parameterToken;
        }
        // 自定义x-access-token
        if (!StringUtils.hasText(virtualToken)) {
            virtualToken = request.getHeader(accessTokenHeaderName);
        }
        if (StringUtils.hasText(virtualToken)) {
            OAuth2Authorization oAuth2AuthorizationServiceByToken =
                oAuth2AuthorizationService.findByToken(virtualToken, OAuth2TokenType.ACCESS_TOKEN);
            if (Objects.nonNull(oAuth2AuthorizationServiceByToken)) {
                return oAuth2AuthorizationServiceByToken.getAttribute("actualToken");
            }
        }
        return virtualToken;
    }

    private String resolveFromAuthorizationHeader(HttpServletRequest request) {
        String authorization = request.getHeader(this.bearerTokenHeaderName);
        if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
            return null;
        }
        Matcher matcher = authorizationPattern.matcher(authorization);
        if (!matcher.matches()) {
            BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed");
            throw new OAuth2AuthenticationException(error);
        }
        return matcher.group("token");
    }

    private static String resolveFromRequestParameters(HttpServletRequest request) {
        String[] values = request.getParameterValues(ACCESS_TOKEN_PARAMETER_NAME);
        if (values == null || values.length == 0) {
            return null;
        }
        if (values.length == 1) {
            return values[0];
        }
        BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");
        throw new OAuth2AuthenticationException(error);
    }

    private boolean isParameterTokenSupportedForRequest(final HttpServletRequest request) {
        return isFormEncodedRequest(request) || isGetRequest(request);
    }

    private static boolean isGetRequest(HttpServletRequest request) {
        return HttpMethod.GET.name().equals(request.getMethod());
    }

    private static boolean isFormEncodedRequest(HttpServletRequest request) {
        return MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType());
    }

    private static boolean hasAccessTokenInQueryString(HttpServletRequest request) {
        return (request.getQueryString() != null) && request.getQueryString().contains(ACCESS_TOKEN_PARAMETER_NAME);
    }

    private boolean isParameterTokenEnabledForRequest(HttpServletRequest request) {
        return (isFormEncodedRequest(request) && !isGetRequest(request) && !hasAccessTokenInQueryString(request)) ||
            isGetRequest(request);
    }
}
